跳到主要内容

1.4-构造类型

Create by fall on 18 Nov 2020 Recently revised in 20 Oct 2023

Object

面向过程和面向对象

  • 面向过程:只考虑数学逻辑
  • 面向对象:将生活逻辑映射到程序中,实现不同的功能功能间相互调用使用
    • 分析有哪些实体
    • 分析设计各个实体的功能
    • 实体间的相互作用

创建对象

对象的拷贝

  • 浅拷贝,拷贝对象的地址,修改对象,会一同修改。
  • 深拷贝,拷贝完整的数据,修改对象,只有当前对象会修改。

JavaScript 拷贝对象时,默认是浅拷贝

// 三种不同的声明方式
var obj1 = new Object();
var obj2 = Object();
var obj3 = {
username : "iron-man"
show:function(){
console.log(this.username)
}
}
// 添加属性和方法
obg3.username = "white house"
obj3.show = function(){}
// 不同的输出方式
alert(obg3.username)
alert(obg3[username])
// 删除方法
delete obg3.username

特性

对象书写的简化

var name = "fall"
var like = 'hobby'
var obj = {
name,
[like]:'discover', // 属性名表达式来定义对象属性相当于 hobby:'discover'
getName(){
return this.name
},
getHobby:()=>{
console.log(this.hobby)
}
}
// JS 解析后等价于下面的书写方法
var obj1 = {
name:"fall",
hobby:'discover',
getName:function(){
return this.name
},
getHobby:()=>{ // 箭头函数解析后还是箭头函数
console.log(this.hobby)
}
}
// 同样的,在获取值时也可以使用表达式
obj[like] // 'discover' // 相当于获取的是 obj.hobby
obj.hobby // 'discover'

super 关键字,指向对象的原型对象,只能用于对象的方法中,其他地方将报错

let person = {
name: 'fall',
getName() {
return super.name
}
}
Object.setPrototypeOf(person, {name: 'hello'})
person.getName()

属性的删除

let staff = {
name:'fall',
age:{
last:11,
cfd:'mmo'
}
}
delete staff.age.last // 此时会删除 staff.age.last
console.log(staff)

对象的遍历

对象只能使用 for..in 进行遍历

// for in
var person = {
username :'iron man',
age:28,
gender:"male"
}
for(var attr in person){
document.write("对象的遍历:"+attr+'分隔'+person[attr]+"<br>")
}

静态方法

Object.fromEntries()Object.entries() 的逆向操作
Object.preventExtensions(obj)让一个对象不可扩展
Object.isExtensible(obj)查看一个对象是否是可扩展的
Object.hasOwn(foo,fuu)(ES2022新增)判断 foo 对象上是否有 fuu 属性
let books = {}
books.prop = 'exists';
// `hasOwn` will only return true for direct properties:
Object.hasOwn(books, 'prop'); // returns true
Object.hasOwn(books, 'toString'); // returns false
Object.hasOwn(books, 'hasOwnProperty'); // returns false

// The `in` operator will return true for direct or inherited properties:
'prop' in books; // returns true
'toString' in books; // returns true
'hasOwnProperty' in books; // returns true

获取键和组成的数组

这些都只会获取可枚举属性,如果想获取不可枚举属性 :Object.getOwnProperty()

let obj = {name:'fall',age:11}
// 生成键的数组
Object.keys(obj) // ['name','age']
// 生成值的数组
Object.values(obj) // ['fall',11]
// 键、值为一个数组,生成多个这样的数组,并通过数组链接
Object.entries(obj) //[[ "name", "fall" ],[ "age", 11 ]]

Object.defineProperty() & Object.defineProperties()

为某个对象添加一个 & 多个自定义属性

let obj = {name:'fall'}
Object.defineProperty(obj,'age',{
value:23,
writable:true,
enumerable:true, // 可枚举
configurable:true
})
Object.defineProperties(obj,{
showName:{
value:function(){console.log(`在下${this.name}有何贵干`)},
writable:true,
enumerable:true, // 可枚举
configurable:true
},
showAge:{
value:function(){console.log(`哥们我今年${this.age}`)},
writable:true,
enumerable:true, // 可枚举
configurable:true
}
})

Object.getOwnPropertyNames(person)

// 因为 for...in 无法循环 Symbol 对象,所以通过该方法查找对象上的Symbol数据
// 返回数组以person对象上所有的Symbol数据构成的数组
let person ={
name:'fall',
[Symbol('age')]:'23',
}
Object.getOwnPropertyNames(person) // [Symbol('age')]

Object.fromEntiries() 逆向操作,将类数组(可迭代对象)转换为对象

const arr = [["name","fall"],["age",23]]
let obj = Object.fromEntries(arr)
obj // { name: "fall", age: 23 }
const ouo = new Map(arr)
let luu = Object.fromEntries(ouo) // { name: "fall", age: 23 }

Object.getOwnPropertyDescriptor(object,key) 获取对象属性上的描述对象,每个对象属性都有一个描述对象

Object.getOwnPropertyDescriptors() 可以获取多个

let person = {name:'布兰',age:12}
Object.getOwnPropertyDescriptor(person,'name')
// { configurable: true, enumerable: true, value: "布兰", writable: true}
Object.getOwnPropertyDescriptor(person)
// {name:{ configurable: true, enumerable: true, value: "布兰", writable: true},
// age:{ configurable: true, enumerable: true, value: 12, writable: true}}
  • Object.assign() 拷贝对象自身的可枚举类型(只拷贝一层,浅拷贝)
const person1 = {
name:'fall',
operation:'front-end-developer'
}
const person2 ={
name:'fall_again',
goodAt:'joking'
}
Object.assign(person1,person2) // person2 会覆盖 person1,
Object.assign(person2,person1) // 用 person1 区覆盖 person2
// 以 Object.assign(person1,person2) 为例返回的值为 person1
// goodAt: "joking"
// name: "fall_again"
// operation: "front-end-developer"
// 此时person2 为
// {name: "fall_again", goodAt: "joking"}
// 注:属性内部的属性不会生效,即,只拷贝一层
// 并且实行的是浅拷贝,如果源对象某个属性是对象,那么拷贝的是这个对象的引用

Object.is() 用来判断两个数字是否相等,表现基本和 === 一样,以下情况除外

+0 === -0            //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Reflect.ownKeys() 返回对象上的所有属性组成的数组

const person1 = {
name:'fall',
operation:'front-end-developer'
}
Reflect.ownKeys(person1) // Array [ "name", "operation" ]

__proto__ 读取和设置当前对象的原型

而由于其下划线更多的是表面其是一个内部属性,所以建议不在正式场合使用它,而是用以下方法代替

  • Object.setPrototypeOf()(写操作)
  • Object.getPrototypeOf()(读操作)
  • Object.create()(生成操作)

Object.setPrototypeOf() 用于设置对象原型,Object.getPrototypeOf() 用于读取对象原型:

let person = {name: 'fall'}
Object.setPrototypeOf(person, {name: 'fall_again'})
Object.getPrototypeOf(person) // {name: 'fall_again'}

实例方法

Object.prototype.hasOwnProperty()

hasOwnProperty() 方法返回一个布尔值,表示对象自有属性(而不是继承来的属性)中是否具有指定的属性。

const object1 = {};
object1.property1 = 42;

console.log(object1.hasOwnProperty('property1'));
// Expected output: true

console.log(object1.hasOwnProperty('toString'));
// Expected output: false

console.log(object1.hasOwnProperty('hasOwnProperty'));
// Expected output: false

const example = {};
example.hasOwnProperty("prop"); // 返回 false

example.prop = "exists";
example.hasOwnProperty("prop"); // 返回 true——“prop”已定义

example.prop = null;
example.hasOwnProperty("prop"); // 返回 true——自有属性存在且值为 null

example.prop = undefined;
example.hasOwnProperty("prop"); // 返回 true——自有属性存在且值为 undefined

Function

可以用来创建类,第二章都是方法(function)相关内容。这里只介绍静态方法和实例方法

特殊用法

剩余 rest 参数的传递...变量名的形式,用于获取函数的剩余参数,注意 rest 参数必须放在最后一个位置,可以很好的代替 arguments 对象:

function foo(x, ...y) {
console.log(x) // 1
for (let val of y) {
console.log(val) // 2 3
}
}
foo(1, 2, 3)
// arguments 对象
function foo2(x, ...y) {
console.log(x) // 1
console.log(arguments) // { 0: 1, 1: 2, 2: 3:3 }
}
foo2(1, 2, 3)

尾调用:函数的相互调用是会生成“调用帧”的,而“调用帧”里存了各种信息比如函数的内部变量和调用函数的位置等,所有的“调用帧”组成了一个“调用栈”。如果在函数的最后一步操作调用了另外一个函数,因为外层函数里调用位置、内部变量等信息都不会再用到了,所有就无需保留外层函数的“调用帧”了,只要直接用内层函数的“调用帧”取代外层函数的“调用帧”即可:

// 是尾调用,就是执行完成 f(x) 之后,执行 g(x)
function f(x) {
return g(x)
}

这样一来就很明显的减少了调用栈中的帧数,内存占用就少了,所以这就是尾调用的优化作用。尾递归也是如此,递归如果次数多那就需要保留非常多的“调用帧”,所以经常会出现栈溢出错误,而使用了尾递归优化后就不会发生栈溢出的错误了。

// 常规递归的斐波那契函数
function Fibonacci(n) {
if ( n <= 1 ) {return 1}
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
Fibonacci(100) // 超时

// 尾递归优化后的斐波那契函数
function Fibonacci2(n, ac1 = 1, ac2 = 1) {
if( n <= 1 ) {return ac2}
return Fibonacci2(n - 1, ac2, ac1 + ac2)
}
Fibonacci2(100) // 573147844013817200000

严格模式:当参数使用了默认值、解构赋值或者扩展运算符时,函数体内就不能显示的设定为严格模式,因为函数执行的顺序是先执行参数,然后再执行函数体因为严格模式的作用范围包含了函数参数,执行到函数体的 use strict 时,函数参数已经执行完成了。

那函数参数还要不要受到严格模式的限制呢,因此出现了矛盾。规避限制的办法有两个:设置全局的严格模式或在函数体外在包一个立即执行函数并且声明严格模式:

// 解法一
'use strict'
function f(x, y = 2) {
}
// 解法二,生成一个立即执行函数
let f = (function(){
'use strict'
return function(x, y = 2) {}
})()

箭头函数

几种箭头函数写法:

let f1 = () => {}               // 没有参数
let f2 = (x) => {} // 1个参数
let f3 = x => {} // 1个参数可以省略圆括号
let f4 = (x, y) => {} // 2个参数以上必须加上圆括号
let f5 = (x = 1, y = 2) => {} // 支持参数默认值
let f6 = (x, ...y) => {} // 支持 rest 参数
let f7 = ({x = 1, y = 2} = {}) // 支持参数解构
let f8 = x => x+7 // 直接 return x+7
let f9 = x =>({name:'fall',bula:x}) // 返回对象时,需要用小括号进行括起来
  • 箭头函数没有自己的 this 指向当前作用域的this
  • 也没有自己的 arguments 对象
  • 箭头函数不能用作构造器,和 new 一起用会抛出错误:
function Feo(){} // function 可以作为构造器 new
let feo = new Feo()

let Foo = () => {}
let foo = new Foo() // TypeError: Foo is not a constructor

argument

argument 本身是一个对象,如果没有参数进行接受,会直接被转入 argument

function fun(){
console.log(argument[0]) // argument[0] 的值为 99
console.log(argument[3]) // undefined
}
fun(99,23,45)

参数尾逗号

允许在参数定义和调用的时候,最后一个参数后添加逗号

function init(a,b,){}
init('12',13,)

async 函数

结合关键字 await 可以实现同步处理异步函数,await 会暂停整个 async 的执行,等成功,或者失败后,才会继续执行。

  • async 函数的声明
// 函数声明
async function request(){}
// 箭头函数声明
let request = async ()=>{}
// 对象方法
let obj ={
async foo(){}
}
  • async 函数的处理

async会返回一个Promise 对象,可以使用 then 进行处理,虽然看起来不像是Promise,但的确是被隐式包含在内。

async function foo(){
return 'a'
}
foo().then(res=>{
res // 'a'
})
// 如果内部出现错误,那么 async 会抛出一个错误,并且后面的 await 都不会执行
let fun = async ()=>{
throw new Error('error')
}
fun.catch(err=>{
err // Error:error
})

实例方法

改变 this 的指向

  • call
  • apply
  • bind
function add(a){
return this + a
}
// apply 和 call 只是传入参数的方式不同
add.call(2,3)
add.apply(2,[3])

需要特别强调一下 bind 的使用

// bind 是返回一个改变 this 后的函数
function add(a){
return this + a
}
const fun = add.bind(2,6)
fun(5)
// 第一个参数只有第一次 bind 会绑定成功
function plus(a,b,c){
return this + a + b + c
}
const after = plus.bind(2,3,5)
const final = after.bind(1000,7)
const result = final()

fun.length

(function(x,y){return x+y}).length // 2 需要传入参数的个数
(function(x,y=1){return x+y}).length // 1
(function(x=1,y){return x+y}).length // 0 如果一个数初始值,那后面的都会忽略

fun.toString()

把方法转换为字符串,并且保留换行、注释、空格

var cc = function(){
console.log('这是一个方法')
}
cc.toString()
// "function(){
// console.log('这是一个方法')
// }"

class

详细内容可见 3.3-面相对象编程

什么是类,什么是对象?

  • :对象特征的集合
  • 对象:具体的某一个事物
// class 是 ES6 中新添加的语法糖,方便定义对象
class Hero{
heroName
level
constructor(heroName,level){ // 可以通过设置默认值(heroName="未知",level="未知")
this.heroName = heroName
this.level = level
}
callMyName(){
console.log(`作为一个英雄,我的名字是${this.heroName},我是${this.level}等级`)
}
}
const bicycle = new Hero('单车骑士',"二级甲等")
bicycle.callMyName()